/*
  AdViEmulator - AdventureVision emulator
  Copyright (C) 2012-2013  JustBurn

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPainter>
#include <QKeyEvent>
#include "traceapuviewbox.h"

TraceAPUViewBox::TraceAPUViewBox(QWidget *parent) :
	QWidget(parent)
{
	avemu = 0;
	topAddr = 0;
	maxAddr = 4096;
	curAddr = 0;
	resize(size());
	scrollProc = true;
}

void TraceAPUViewBox::attachEmu(AdViEmulator *emu, QScrollBar *sb)
{
	avemu = emu;
	viewsb = sb;
	connect(sb, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
}

void TraceAPUViewBox::setTopAddress(int value)
{
	if (value < 0) value = 0;
	if (value > maxAddr) value = maxAddr - 1;
	topAddr = value;
	scrollProc = false;
	viewsb->setValue(-topAddr);
	scrollProc = true;
	repaint();
}

void TraceAPUViewBox::moveCursor(int addr, bool refresh)
{
	if (addr < 0) addr = 0;
	if (addr >= maxAddr) addr = maxAddr-1;
	curAddr = addr;
	int EndAddr = topAddr + numLines - 1;
	if (curAddr < topAddr) topAddr = curAddr;
	if (curAddr >= EndAddr) topAddr = curAddr - (numLines - 1);
	if (topAddr < 0) topAddr = 0;
	if (topAddr >= maxAddr) topAddr = maxAddr-1;
	scrollProc = false;
	viewsb->setValue(-topAddr);
	scrollProc = true;
	if (refresh) repaint();
}

void TraceAPUViewBox::paintEvent(QPaintEvent *)
{
	QPainter painter(this);
	QBrush cursor(hasFocus() ? QColor::fromRgb(255, 160, 64) : QColor::fromRgb(160, 160, 160), Qt::Dense4Pattern);

	// Draw white background
	painter.setBrush(Qt::white);
	painter.setPen(Qt::transparent);
	painter.drawRect(0, 0, width(), height());
	painter.setBrush(Qt::transparent);
	painter.setPen(Qt::black);

	// Get metrics
	int twidth = painter.fontMetrics().width('0');
	int theight = painter.fontMetrics().height();

	// Draw memory content
	int vaddr = topAddr;
	for (int y=0; y<numLines+1; y++) {
		if (vaddr >= maxAddr) break;
		int ypos = height() - y * theight - 2;

		// Draw cursor
		if (curAddr == vaddr) {
			painter.setBrush(cursor);
			painter.setPen(Qt::transparent);
			painter.drawRect(0, height() - (y + 1) * theight + 3, width(), theight);
			painter.setPen(Qt::black);
		}

		// Write offset
		QString offstr = QString::number(-vaddr - 1);
		offstr = QString().fill(' ', 5 - offstr.length()) + offstr;
		painter.drawText(5, ypos, offstr + ":");

		// Write address
		uint16_t iraddr;
		avemu->GetAPUTrace(vaddr++, &iraddr);
		if (iraddr >= 512) {
			painter.drawText(5 + twidth * 7, ypos, "-----  -----");
			continue;
		}
		QString addrstr = QString::number(iraddr, 16).toUpper();
		addrstr = QString().fill('0', 3 - addrstr.length()) + addrstr;
		painter.drawText(5 + twidth * 7, ypos, " @" + addrstr);

		// Write instruction
		char Instructi[64];
		avemu->apu.GetInstructionName(iraddr, Instructi);
		painter.drawText(5 + twidth * 14, ypos, Instructi);
	}
}

void TraceAPUViewBox::resizeEvent(QResizeEvent *event)
{
	numLines = event->size().height() / this->fontMetrics().height();
	viewsb->setMaximum(0);
	viewsb->setMinimum(-(maxAddr - numLines));
}

void TraceAPUViewBox::keyPressEvent(QKeyEvent *event)
{
	switch (event->key()) {
	case Qt::Key_Up:
		moveCursor(curAddr + 1);
		break;
	case Qt::Key_Down:
		moveCursor(curAddr - 1);
		break;
	case Qt::Key_PageUp:
		moveCursor(curAddr + numLines/2);
		break;
	case Qt::Key_PageDown:
		moveCursor(curAddr - numLines/2);
		break;
	case Qt::Key_Home:
		moveCursor(0);
		break;
	case Qt::Key_End:
		moveCursor(maxAddr-1);
		break;
	default:
		return;
	}
	repaint();
}

void TraceAPUViewBox::mousePressEvent(QMouseEvent *event)
{
	int mcy = ((height() - event->pos().y()) + 3) / this->fontMetrics().height();
	if ((mcy >= 0) && (mcy < numLines)) {
		moveCursor(topAddr + mcy, true);
	}
}

void TraceAPUViewBox::mouseDoubleClickEvent(QMouseEvent *event)
{
	int mcy = ((height() - event->pos().y()) + 3) / this->fontMetrics().height();
	if ((mcy >= 0) && (mcy < numLines)) {
		moveCursor(topAddr + mcy, true);
		if (event->button() == Qt::LeftButton) {
			uint16_t iraddr;
			avemu->GetAPUTrace(curAddr, &iraddr);
			if (iraddr < 512) {
				emit goToAPUAddress(iraddr);
			}
		}
	}
}

void TraceAPUViewBox::wheelEvent(QWheelEvent *event)
{
	if (event->delta() > 0) {
		setTopAddress(topAddr + numLines/2);
	} else {
		setTopAddress(topAddr - numLines/2);
	}
}

void TraceAPUViewBox::scrollChanged(int value)
{
	if (scrollProc) setTopAddress(-value);
}
